import { storeEdgesRenderEdge, storeNode2FlowNode } from "@/function/utils";
import { useMemoizedFn } from "ahooks";
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
import { useEdgesState, useNodesState } from "reactflow";
import { AppDetailContext } from "./app/detail/appDetailContext";
import { NodeOutputKeyEnum } from "@/enums/NodeOutputKeyEnum";
import { DebugDataType } from "@/enums/DebugDataType";
import { eventBus, EventNameEnum } from "@/function/eventbus";

interface WorkflowContextType {
  splitToolInputs: any; // 请根据实际需求替换any为具体类型
  nodes:any,
  edges:any,
  nodeLists:any,
  initData:any,
  onNodesChange:any,
  basicNodeTemplates:any,
  setEdges:any,
  setNodes:any,
  reactFlowWrapper:any,
  onStartNodeDebug:any,
  onNextNodeDebug:any,
  workflowDebugData:any,
  onStopNodeDebug:any,
  hoverNodeId?: string;
  setHoverNodeId:any
  // ... 其他属性 ...
} 

export const WorkflowContext = createContext<WorkflowContextType>({
    nodes: [],
    edges: [],
    onNodesChange:(e:any)=>{ },
    basicNodeTemplates:[],
    nodeLists:[],
  
    reactFlowWrapper:null,
    splitToolInputs:(inputs:any,)=>{

    },
    setEdges: (e:any)=>{},
    setNodes: (e:any)=>{},
    initData: (e:any)=>{ },
    workflowDebugData:undefined,
    onStartNodeDebug:()=>{
    },
    onNextNodeDebug:()=>{},
    onStopNodeDebug:()=>{},
    setHoverNodeId:()=>{}
});




const  WorkflowContextProvider = ({children,basicNodeTemplates}:{children:React.ReactNode,basicNodeTemplates:any}) => {

    // const { setAppDetail } = useContext(AppDetailContext);

    const [edges, setEdges, onEdgesChange] = useEdgesState([]);
    const [hoverNodeId, setHoverNodeId] = useState<string>();
    const [nodes = [], setNodes, onNodesChange] = useNodesState([]);
    const reactFlowWrapper = useRef<HTMLDivElement>(null);
    const splitToolInputs = (inputs: any[], nodeId: string) => {
        const isTool = !!edges.find(
          (edge) => edge.targetHandle === NodeOutputKeyEnum.selectedTools && edge.target === nodeId
        );
        return {
          isTool,
          toolInputs: inputs.filter((item) => isTool && item.toolDescription),
          commonInputs: inputs.filter((item) => {
            if (!isTool) return true;
            return !item.toolDescription;
          })
        };
      };
    

      
    const initData = useMemoizedFn(async (e:any) => {
        setNodes(e.nodes?.map((item:any) => storeNode2FlowNode(item)) || []);
        setEdges(e.edges?.map((item:any) => storeEdgesRenderEdge(item)) || []);


        // const chatConfig = e.chatConfig;
        // if (chatConfig) {
        //   setAppDetail((state) => ({
        //     ...state,
        //     chatConfig
        //   }));
        // }
     });

    // const workflowStringData = JSON.stringify({
    //     nodes: appDetail.modules || [],
    //     edges: appDetail.edges || []
    // });
    const [workflowDebugData, setWorkflowDebugData] = useState<DebugDataType>();

    const onStopNodeDebug = useCallback(() => {
      setWorkflowDebugData(undefined);
      setNodes((state) =>
        state.map((node) => ({
          ...node,
          selected: false,
          data: {
            ...node.data,
            debugResult: undefined
          }
        }))
      );
    }, []);
      
    const onNextNodeDebug = useCallback(
      async (debugData = workflowDebugData) => {
        if (!debugData) return;
        // 1. Cancel node selected status and debugResult.showStatus
        setNodes((state) =>
          state.map((node) => ({
            ...node,
            selected: false,
            data: {
              ...node.data,
              debugResult: node.data.debugResult
                ? {
                    ...node.data.debugResult,
                    showResult: false,
                    isExpired: true
                  }
                : undefined
            }
          }))
        );
  
        // 2. Set isEntry field and get entryNodes
        const runtimeNodes = debugData.runtimeNodes.map((item) => ({
          ...item,
          isEntry: debugData.nextRunNodes.some((node) => node.nodeId === item.nodeId)
        }));
        const entryNodes = runtimeNodes.filter((item) => item.isEntry);
  
        const runtimeNodeStatus: Record<string, string> = entryNodes
          .map((node) => {
            const status = checkNodeRunStatus({
              node,
              runtimeEdges: debugData?.runtimeEdges || []
            });
  
            return {
              nodeId: node.nodeId,
              status
            };
          })
          .reduce(
            (acc, cur) => ({
              ...acc,
              [cur.nodeId]: cur.status
            }),
            {}
          );
  
        // 3. Set entry node status to running
        entryNodes.forEach((node) => {
          if (runtimeNodeStatus[node.nodeId] !== 'wait') {
            onChangeNode({
              nodeId: node.nodeId,
              type: 'attr',
              key: 'debugResult',
              value: defaultRunningStatus
            });
          }
        });
  
        try {
          // 4. Run one step
          const { finishedEdges, finishedNodes, nextStepRunNodes, flowResponses } =
            await postWorkflowDebug({
              nodes: runtimeNodes,
              edges: debugData.runtimeEdges,
              variables: {},
              appId,
              pluginId
            });
          // console.log({ finishedEdges, finishedNodes, nextStepRunNodes, flowResponses });
          // 5. Store debug result
          const newStoreDebugData = {
            runtimeNodes: finishedNodes,
            // edges need to save status
            runtimeEdges: finishedEdges.map((edge) => {
              const oldEdge = debugData.runtimeEdges.find(
                (item) => item.source === edge.source && item.target === edge.target
              );
              const status =
                oldEdge?.status && oldEdge.status !== RuntimeEdgeStatusEnum.waiting
                  ? oldEdge.status
                  : edge.status;
              return {
                ...edge,
                status
              };
            }),
            nextRunNodes: nextStepRunNodes
          };
          setWorkflowDebugData(newStoreDebugData);
  
          // 6. selected entry node and Update entry node debug result
          setNodes((state) =>
            state.map((node) => {
              const isEntryNode = entryNodes.some((item) => item.nodeId === node.data.nodeId);
  
              if (!isEntryNode || runtimeNodeStatus[node.data.nodeId] === 'wait') return node;
  
              const result = flowResponses.find((item) => item.nodeId === node.data.nodeId);
  
              if (runtimeNodeStatus[node.data.nodeId] === 'skip') {
                return {
                  ...node,
                  selected: isEntryNode,
                  data: {
                    ...node.data,
                    debugResult: {
                      status: 'skipped',
                      showResult: true,
                      isExpired: false
                    }
                  }
                };
              }
              return {
                ...node,
                selected: isEntryNode,
                data: {
                  ...node.data,
                  debugResult: {
                    status: 'success',
                    response: result,
                    showResult: true,
                    isExpired: false
                  }
                }
              };
            })
          );
  
          // Check for an empty response
          if (flowResponses.length === 0 && nextStepRunNodes.length > 0) {
            onNextNodeDebug(newStoreDebugData);
          }
        } catch (error) {
          entryNodes.forEach((node) => {
            onChangeNode({
              nodeId: node.nodeId,
              type: 'attr',
              key: 'debugResult',
              value: {
                status: 'failed',
                message: getErrText(error, 'Debug failed'),
                showResult: true
              }
            });
          });
          console.log(error);
        }
      },
      []
    );

    const onStartNodeDebug = useCallback(
      async ({
        entryNodeId,
        runtimeNodes,
        runtimeEdges
      }: {
        entryNodeId: string;
        runtimeNodes: any;
        runtimeEdges:any;
      }) => {
        const data = {
          runtimeNodes,
          runtimeEdges,
          nextRunNodes: runtimeNodes.filter((node) => node.nodeId === entryNodeId)
        };
        onStopNodeDebug();
        setWorkflowDebugData(data);
  
        onNextNodeDebug(data);
      },
      [onNextNodeDebug, onStopNodeDebug]
    );
    
    useEffect(() => {
      eventBus.on(EventNameEnum.requestWorkflowStore, () => {
        eventBus.emit(EventNameEnum.receiveWorkflowStore, {
          nodes
        });
      });
      return () => {
        eventBus.off(EventNameEnum.requestWorkflowStore);
      };
    }, [nodes]);


      const nodeLists = nodes;
    // const { appId, pluginId } = value;
    return (
        <WorkflowContext.Provider value={{
            // appId,
            nodes,
            edges,
            nodeLists,
            setEdges,
            setNodes,
            hoverNodeId,
            setHoverNodeId,
            reactFlowWrapper,
            basicNodeTemplates,
            onNodesChange,
            onEdgesChange,
            splitToolInputs,
            initData,
        }}>
            {children}
         </WorkflowContext.Provider>
    )
}


export default WorkflowContextProvider;

type GetWorkflowStoreResponse = {
  nodes: any;
};

export const getWorkflowStore = () =>
  new Promise<GetWorkflowStoreResponse>((resolve) => {
    eventBus.on(EventNameEnum.receiveWorkflowStore, (data: GetWorkflowStoreResponse) => {
      resolve(data);
      eventBus.off(EventNameEnum.receiveWorkflowStore);
    });
    eventBus.emit(EventNameEnum.requestWorkflowStore);
  });
